/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "serial_async_callback.h"
#include <cassert>
#include <unistd.h>

#include "uv.h"

#include "log/serialport_log_wrapper.h"
#include "i_serialport_client.h"

namespace OHOS {
namespace SerialPort {
SerialAsyncCallback::~SerialAsyncCallback()
{
    if (env_ != 0 && funcRef_ != 0) {
        napi_delete_reference(env_, funcRef_);
        funcRef_ = 0;
    }
    std::lock_guard<std::mutex> lock(mutex_);
    name_ = "";
    while (!buffer_.empty()) {
        SerialData *data = buffer_.front();
        buffer_.pop();
        free(data);
    }
}
void SerialAsyncCallback::SetEnv(const std::string &name, napi_env env, napi_value func)
{
    name_ = name;
    env_ = env;
    napi_status result_status = napi_create_reference(env_, func, 1, &funcRef_);
    if (result_status != napi_ok) {
        SERIALPORT_LOGE("serial '%{public}s' setenv failed", name_.c_str());
    }
}

void SerialAsyncCallback::OnCallBackEvent()
{
    do {
        if (buffer_.empty()) {
            break;
        }
        struct AsyncData {
            std::shared_ptr<SerialCallbackBase> callback;
        };
        AsyncData *asyncData = new(std::nothrow) AsyncData;
        if (asyncData == nullptr) {
            break;
        }
        asyncData->callback = shared_from_this();
        uv_loop_s *loop = nullptr;
        uv_work_t *work = new(std::nothrow) uv_work_t;
        if (work == nullptr) {
            delete asyncData;
            break;
        }
        napi_get_uv_event_loop(env_, &loop);
        work->data = (void *)asyncData;
        int32_t ret = uv_queue_work(loop, work, [](uv_work_t *) {}, [](uv_work_t *work, int status) {
            AsyncData *callData = (AsyncData *) work->data;
            std::shared_ptr<SerialCallbackBase> callback = callData->callback;
            int32_t delay = ((SerialAsyncCallback *)callback.get())->OnUvWork();
            get_serial_client()->SendCallbackEvent(callback, delay);
            delete callData;
            delete work;
        });
        if (ret == 0) {
            return;  // work don't set busy_
        }
        SERIALPORT_LOGE("serial '%{public}s' queue_work failed", name_.c_str());
        delete asyncData;
        delete work;
    } while(0);
    busy_ = false;
}

int32_t SerialAsyncCallback::OnUvWork() {
    SerialData *buffer = nullptr;
    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!buffer_.empty()) {
            buffer = buffer_.front();
            buffer_.pop();
        }
    }
    if (buffer == nullptr) {
        SERIALPORT_LOGI("serial uv_work buffer null");
        return 10;
    }

    napi_handle_scope scope = nullptr;
    napi_open_handle_scope(env_, &scope);
    SERIALPORT_LOGI("serial '%{public}s' uv_work fun", name_.c_str());

    napi_value cb = 0;
    napi_status result_status = napi_get_reference_value(env_, funcRef_, &cb);
    if (result_status != napi_ok) {
        SERIALPORT_LOGE("serial '%{public}s' uv_work get fun ref failed", name_.c_str());
    }

    napi_value thisVar = 0;
    napi_value outData;
    napi_value arrayBuffer = nullptr;
    napi_create_external_arraybuffer(env_, (void *)buffer->data, buffer->length,
        [](napi_env env, void *finalize_data, void *finalize_hint) {
            free(finalize_hint);
        }, buffer, &arrayBuffer);
    napi_create_typedarray(env_, napi_uint8_array, buffer->length, arrayBuffer, 0, &outData);
    napi_value args[1] = {outData};
    napi_value cb_result;
    napi_get_undefined(env_, &thisVar);
    result_status = napi_call_function(env_, thisVar, cb, 1, args, &cb_result);
    if (result_status != napi_ok) {
        SERIALPORT_LOGE("serial '%{public}s' uv_work napi_call_function failed", name_.c_str());
    }
    napi_close_handle_scope(env_, scope);
    return 5; // delay 5 ms
}

void SerialAsyncCallback::OnRecvData(const uint8_t *buffer, uint32_t length)
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (name_.length() <= 0) {
        SERIALPORT_LOGI("serial name is empty");
        return;
    }
    SerialData *old = nullptr;
    if (!buffer_.empty()) {
        old = buffer_.back();
        if (old != nullptr && (old->length + length <= MIN_BUFFER_SIZE)) {
            memcpy(&old->data[old->length], buffer, length);
            old->length += length;
        } else {
            old = nullptr;
        }
    }
    if (old == nullptr) {
        uint32_t newLen = sizeof(uint32_t) + (length<MIN_BUFFER_SIZE?MIN_BUFFER_SIZE:length);
        SerialData *recv = (SerialData *)malloc(newLen);
        if (recv == nullptr) {
            SERIALPORT_LOGE("serial %{public}s allocate memory failed", name_.c_str());
            return;
        }
        recv->length = length;
        memcpy(recv->data, buffer, length);
        buffer_.push(recv);
    }
    if (!busy_) {
        busy_ = true;
        get_serial_client()->SendCallbackEvent(shared_from_this(), 0);
    }
    SERIALPORT_LOGI("serial %{public}s buffer size %{public}d", name_.c_str(), buffer_.size());
    return;
}

} // namespace SerialPort
} // namespace OHOS